/**
 * 
 */
package pt.inescid.core;

import java.io.File;
import java.util.List;
import java.util.Vector;

import pt.inescid.components.api.CommunicationServiceI;
import pt.inescid.components.api.JobManagerI;
import pt.inescid.components.api.OverlayManagerI;
import pt.inescid.components.api.PolicyEngineI;
import pt.inescid.components.appadaptationlayer.AppAdaptationLayer;
import pt.inescid.components.commons.FrontEnd;
import pt.inescid.components.commons.Resources;
import pt.inescid.components.communicationservice.CommunicationService;
import pt.inescid.components.console.Console;
import pt.inescid.components.gui.GUI;
import pt.inescid.components.jobmanager.JobManager;
import pt.inescid.components.messages.ACKMsg;
import pt.inescid.components.messages.AvailabilityMsg;
import pt.inescid.components.messages.Task;
import pt.inescid.components.messages.TaskResult;
import pt.inescid.components.messages.UpdateMsg;
import pt.inescid.components.overlaymanager.NodeMetrics;
import pt.inescid.components.overlaymanager.OverlayManager;
import pt.inescid.components.policyengine.PolicyEngine;
import pt.inescid.utils.Log;
import pt.inescid.utils.ReadXML;

import rice.environment.Environment;
import rice.p2p.commonapi.Endpoint;
import rice.p2p.commonapi.Id;
import rice.p2p.commonapi.Message;
import rice.p2p.commonapi.NodeHandle;
import rice.pastry.PastryNode;



/**
 * 
 * TODO:
 * - classpath in jar not well configured
 * 
 * - ckeck this interface: http://unu.novajo.ca/simple/archives/000024.html
 * 
 * - on node leave give the task that other node
 * 
 * - IMPORTANT: http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/renderexamples/
 * 
 * @author Sergio Esteves (sesteves at gsd.inesc-id.pt)
 *
 */
public class GridP2P implements JobManagerI,PolicyEngineI,OverlayManagerI,CommunicationServiceI {

	protected final static String CONFIG_FILE = "config.xml";
	protected final static String GUI_STR = "gui";
	protected final static String ANYTIME_STR = "anytime";
	
	protected static String mode = GUI_STR;
	protected static int bindPort = 9001;
	protected static String bootIP = "localhost";
	protected static int bootPort = 9001;
	protected static double minRatio = 0.5;
	protected static int lboundHour = -1;
	protected static int uboundHour = 0;
	protected static long idleTime = 0;
	protected static long maxStorage = 209715200; // 200 MB
	protected static int maxConcurrentTasks = 1;
	protected static long updateInterval = 1800000; // half an hour
	protected static long minTaskSize = 1024;
	protected static long maxTaskSize = 1073741824;
	protected static String tempDir = "./tmp/";
	protected static String logFile = "GridP2P.log";
	protected static int logLevel = Log.FINEST;
	
	public Log log;		
	
	private static FrontEnd frontEnd;
	private static AppAdaptationLayer appAdaptationLayer;
	private static JobManager jobManager;
	private static PolicyEngine policyEngine;
	private static OverlayManager overlayManager;
	private static CommunicationService communicationService;
		
	public GridP2P() {

		//Reading the configuration file
		ReadXML readXML = new ReadXML(CONFIG_FILE);
		String tag="";

		if((tag = readXML.getTag("mode", 0)) != null) mode = tag;
		if((tag = readXML.getTag("bindPort", 0)) != null) bindPort = Integer.parseInt(tag);
		if((tag = readXML.getTag("bootIP", 0)) != null) bootIP = tag;
		if((tag = readXML.getTag("bootPort", 0)) != null) bootPort = Integer.parseInt(tag);
		if((tag = readXML.getTag("minRatio", 0)) != null) minRatio = Double.parseDouble(tag);
		if((tag = readXML.getTag("lboundHour", 0)) != null)
			lboundHour = ANYTIME_STR.equals(tag) ? -1 : Integer.parseInt(tag);
		if((tag = readXML.getTag("uboundHour", 0)) != null) uboundHour = Integer.parseInt(tag);
		if((tag = readXML.getTag("idleTime", 0)) != null) idleTime = Long.parseLong(tag) * 60000;		
		if((tag = readXML.getTag("maxStorage", 0)) != null) maxStorage = Long.parseLong(tag);
		if((tag = readXML.getTag("maxConcurrentTasks", 0)) != null) 
			maxConcurrentTasks = Integer.parseInt(tag);
		if((tag = readXML.getTag("updateInterval", 0)) != null) updateInterval = Long.parseLong(tag);
		if((tag = readXML.getTag("minTaskSize", 0)) != null) minTaskSize = Long.parseLong(tag);
		if((tag = readXML.getTag("maxTaskSize", 0)) != null) maxTaskSize = Long.parseLong(tag);
		
		if((tag = readXML.getTag("tempDir", 0)) != null) tempDir = tag;
		if((tag = readXML.getTag("logLevel", 0)) != null)
			try {
				logLevel = Log.class.getDeclaredField(tag).getInt(null);
			} catch (IllegalArgumentException e) {
			} catch (SecurityException e) {
			} catch (IllegalAccessException e) {
			} catch (NoSuchFieldException e) {
			}
		if((tag = readXML.getTag("logFile", 0)) != null) logFile = tag;
		//done reading the configuration file
		
		log = new Log(logFile, logLevel);
		log.write("---------- BEGIN LOGGING ---------- (Level: " + logLevel +
				", Java version: " + System.getProperty("java.version") +
				")", Log.SEVERE);		
		
		//Initializing the FrontEnd
		if(GUI_STR.equals(mode))
			frontEnd = new GUI(this, lboundHour, uboundHour, idleTime, maxStorage);
		else
			frontEnd = new Console(this);
		
		//creation of components 
		appAdaptationLayer = new AppAdaptationLayer(this, tempDir);
		jobManager = new JobManager(this, minTaskSize, maxTaskSize, tempDir);
		policyEngine = new PolicyEngine(this, lboundHour, uboundHour, maxConcurrentTasks,idleTime == 0, minRatio);
		overlayManager = new OverlayManager(this, updateInterval, tempDir, maxStorage);		
		communicationService = new CommunicationService(this);
	}

	
	public void init() {
		//creation of TEMP directory.
		File tmpF = new File(tempDir);
		if(!tmpF.exists()) tmpF.mkdirs();
		
		//initialization of components
		appAdaptationLayer.init();
		frontEnd.init();
		jobManager.init();
		policyEngine.init();
		overlayManager.init(bindPort, bootIP, bootPort);		

		writeln("System Initialized!");
		log.write(this, "GridP2P initialized", Log.SEVERE);
	}
	
	public static void main(String[] args) {
		new GridP2P().init();
	}
	
	/* 
	 * FrontEnd Methods
	 */
	public void writeln(String str) {
		frontEnd.writeln(str);
	}
	
	/*
	 * Application Adaptation Layer
	 */
	public String getDataLocation(String appID, List<String> input) {
		return appAdaptationLayer.getDataLocation(appID, input);
	}
	public String getConfigLocation(String appID, List<String> input) {
		return appAdaptationLayer.getConfigLocation(appID, input);
	}
	public byte[] buildConfig(String appID, String configLocation, int n, int nTasks) {
		return appAdaptationLayer.buildConfig(appID, configLocation, n, nTasks);
	}
	public byte[] buildOutput(String appID, Vector<byte[]> slices) {
		return appAdaptationLayer.buildOutput(appID, slices);
	}
	public boolean buildOutput(String appID, int index, String configLocation, String outputLocation) {
		return appAdaptationLayer.buildOutput(appID, index, configLocation, outputLocation);
	}
	public Resources launchAppTrial(String appID, String configLocation, String dataLocation) {
		return appAdaptationLayer.launchAppTrial(appID, configLocation, dataLocation);
	}
	public String launchApp(String appID, String configLocation, String dataLocation) {
		return appAdaptationLayer.launchApp(appID, configLocation, dataLocation);
	}	
	public String launchApp(String appID, byte[] config, String outputLocation) {
		return appAdaptationLayer.launchApp(appID, config, outputLocation);
	}
	public void createState(String appID, int index, String configLocation) {
		appAdaptationLayer.createState(appID, index, configLocation);
	}
	public void updateState(String appID, int index, byte[] data) {
		appAdaptationLayer.updateState(appID, index, data);
	}
	public String getConfigExt(String appID) {
		return appAdaptationLayer.getConfigExt(appID);
	}
	public String getDataExt(String appID) {
		return appAdaptationLayer.getDataExt(appID);
	}
	public String[] getAppIDs() {
		return appAdaptationLayer.getAppIDs();
	}	
	public boolean isStream(String appID) {
		return appAdaptationLayer.isStream(appID);
	}
	
	/**
	 * Job Manager Methods
	 */ 
	public int createJob(String appID, List<String> input, String output) {
		return jobManager.createJob(appID, input, output);
	}	
	public void processTask(Id id, Task message, long currentTime) {
		jobManager.processTask(id, message, currentTime);
	}	
	public void gatherTaskResult(Id id, TaskResult message, long currentTime) {
		jobManager.gatherTaskResult(id, message, currentTime);
	}
	public int getNumTasksInProgress() {
		return jobManager.getNumTasksInProgress();
	}
	
	/**
	 * Policy Engine Methods
	 */
	public boolean controlInbound(Message message) {
		return policyEngine.controlInbound(message);
	}
	public boolean controlOutbound(Message message) {
		return policyEngine.controlOutbound(message);
	}
	public boolean precontrolOutbound(NodeMetrics nm) {
		return policyEngine.precontrolOutbound(nm);
	}
	public void setLboundHour(int hour) {
		policyEngine.setLboundHour(hour);
	}
	public void setUboundHour(int hour) {
		policyEngine.setUboundHour(hour);
	}
	public void setIsIdle(boolean isIdle) {
		policyEngine.setIsIdle(isIdle);
	}
	
	/**
	 * Overlay Manager Methods
	 */ 
	public void updateResources(double cpu, double mem, double bw) {
		overlayManager.updateResources(cpu, mem, bw);
	}
	public void updateResources() {
		overlayManager.updateResources();
	}
	public int getNumAvailableNodes(String appID, int limit) {
		return overlayManager.getNumAvailableNodes(appID, limit);
	}
	public int distributeTasks(String appID, List<Task> tasks, Resources taskCost) {
		return overlayManager.distributeTasks(appID, tasks, taskCost);
	}	
	public int sendResult(Id id, int jobHandler, int taskNumber, byte[] output, double workDone) {
		return overlayManager.sendResult(id, jobHandler, taskNumber, output, workDone);
	}
	public PastryNode getNode() {
		return overlayManager.getNode();
	}
	public Environment getEnvironment() {
		return overlayManager.getEnvironment();
	}
	public boolean networkInitialized() {
		return overlayManager.networkInitialized();
	}
	
	public void joinNode(NodeHandle handle) {
		overlayManager.joinNode(handle);
	}
	public void leaveNode(NodeHandle handle) {
		overlayManager.leaveNode(handle);
	}
	
	public void messageReceived(Id id, UpdateMsg message) {
		overlayManager.messageReceived(id, message);
	}
	public void messageReceived(Id id, ACKMsg message) {
		overlayManager.messageReceived(id, message);
	}
	public void messageReceived(Id id, Task message, long currentTime) {
		overlayManager.messageReceived(id, message, currentTime);
	}
	public void messageReceived(Id id, TaskResult message, long currentTime) {
		overlayManager.messageReceived(id, message, currentTime);
	}	
	public void messageReceived(Id id, AvailabilityMsg message) {
		overlayManager.messageReceived(id, message);
	}
	public UpdateMsg buildUpdateMsg() {
		return overlayManager.buildUpdateMsg();
	}
	public Id forwardMessage(Id id, Message message) {
		return overlayManager.forwardMessage(id, message);
	}
	public void cacheTaskResult(Id key, final byte[] result) {
		overlayManager.cacheTaskResult(key, result);
	}
	public byte[] lookupCache(final Id lookupKey) {
		return overlayManager.lookupCache(lookupKey);
	}
	
	public long getMaxStorage() {
		return overlayManager.getMaxStorage();
	}
	public void setMaxStorage(long size) {
		overlayManager.setMaxStorage(size);
	}
	
	/**
	 * Communication Service Methods
	 */ 
	public void registerEndPoint() {
		communicationService.registerEndPoint();
	}
	public void routeMyMsg(Id id, Message message) {
		communicationService.routeMyMsg(id, message);
	}
	public Endpoint getEndpoint() {
		return communicationService.getEndpoint();
	}
	
	/**
	 * Testing purposes
	 */
	public int getLboundHour() {
		return policyEngine.getLboundHour();
	}
	public int getUboundHour() {
		return policyEngine.getUboundHour();
	}
	public double getMinRatio() {
		return policyEngine.getMinRatio();
	}
	public double getRatio() {
		return policyEngine.getRatio();
	}
	public void setMinRatio(double minRatio) {
		policyEngine.setMinRatio(minRatio);
	}
	public void setRatio(double ratio) {
		policyEngine.setRatio(ratio);
	}

	
	public String getNeighborsDescription() {
		return overlayManager.getNeighborsDescription();
	}
	public String getResourceManifest() {
		return overlayManager.getResourceManifest();
	}
	
	public void setNumTasksInProgress(int value) {
		jobManager.setNumTasksInProgress(value);
	}
	public int getNMaxCachedTasks() {
		return jobManager.getNMaxCachedTasks();
	}	
	public void setNMaxCachedTasks(int maxN) {
		jobManager.setNMaxCachedTasks(maxN);
	}	
}
